/*
 * ghttp.c -- Implementation of the public interface to http functions
 * Created: Christopher Blizzard <blizzard@appliedtheory.com>, 21-Aug-1998
 *
 * Copyright (C) 1998 Free Software Foundation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "ghttp.h"
#include "http_base64.h"
#include "http_date.h"
#include "http_global.h"
#include "http_hdrs.h"
#include "http_req.h"
#include "http_resp.h"
#include "http_trans.h"
#include "http_uri.h"
#include "rt_print.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct _ghttp_request
{
    http_uri *       uri;
    http_uri *       proxy;
    http_req *       req;
    http_resp *      resp;
    http_trans_conn *conn;
    const char *     errstr;
    int              connected;
    ghttp_proc       proc;
    char *           username;
    char *           password;
    char *           authtoken;
    char *           proxy_username;
    char *           proxy_password;
    char *           proxy_authtoken;
};

static const char *basic_header = "Basic ";

ghttp_request *ghttp_request_new(void)
{
    struct _ghttp_request *l_return = NULL;

    /* create everything */
    l_return = malloc(sizeof(struct _ghttp_request));
    memset(l_return, 0, sizeof(struct _ghttp_request));
    l_return->uri   = http_uri_new();
    l_return->proxy = http_uri_new();
    l_return->req   = http_req_new();
    l_return->resp  = http_resp_new();
    l_return->conn  = http_trans_conn_new();
    return l_return;
}

void ghttp_request_destroy(ghttp_request *a_request)
{
    if (!a_request)
        return;
    /* make sure that the socket was shut down. */
    if (a_request->conn->sock >= 0)
    {
        close(a_request->conn->sock);
        a_request->conn->sock = -1;
    }
    /* destroy everything else */
    if (a_request->uri)
        http_uri_destroy(a_request->uri);
    if (a_request->proxy)
        http_uri_destroy(a_request->proxy);
    if (a_request->req)
        http_req_destroy(a_request->req);
    if (a_request->resp)
        http_resp_destroy(a_request->resp);
    if (a_request->conn)
        http_trans_conn_destroy(a_request->conn);
    /* destroy username info. */
    if (a_request->username)
    {
        free(a_request->username);
        a_request->username = NULL;
    }
    if (a_request->password)
    {
        free(a_request->password);
        a_request->password = NULL;
    }
    if (a_request->authtoken)
    {
        free(a_request->authtoken);
        a_request->authtoken = NULL;
    }
    /* destroy proxy authentication */
    if (a_request->proxy_username)
    {
        free(a_request->proxy_username);
        a_request->proxy_username = NULL;
    }
    if (a_request->proxy_password)
    {
        free(a_request->proxy_password);
        a_request->proxy_password = NULL;
    }
    if (a_request->proxy_authtoken)
    {
        free(a_request->proxy_authtoken);
        a_request->proxy_authtoken = NULL;
    }
    if (a_request)
        free(a_request);
    return;
}

int ghttp_uri_validate(char *a_uri)
{
    if (!a_uri)
        return -1;
    /* you can do this... */
    return (http_uri_parse(a_uri, NULL));
}

int ghttp_set_uri(ghttp_request *a_request, char *a_uri)
{
    int       l_rv      = 0;
    http_uri *l_new_uri = NULL;

    if ((!a_request) || (!a_uri))
        return -1;
    /* set the uri */
    l_new_uri = http_uri_new();
    l_rv      = http_uri_parse(a_uri, l_new_uri);

    if (l_rv < 0)
    {
        http_uri_destroy(l_new_uri);
        return -1;
    }
    if (a_request->uri)
    {
        /* check to see if this has been set yet. */
        if (a_request->uri->host && a_request->uri->port &&
            a_request->uri->resource)
        {
            /* check to see if we just need to change the resource */
            if ((!strcmp(a_request->uri->host, l_new_uri->host)) &&
                (a_request->uri->port == l_new_uri->port))
            {
                free(a_request->uri->resource);
                /* make a copy since we're about to destroy it */
                a_request->uri->resource = strdup(l_new_uri->resource);
                http_uri_destroy(l_new_uri);
            }
            else
            {
                http_uri_destroy(a_request->uri);
                a_request->uri = l_new_uri;
            }
        }
        else
        {
            http_uri_destroy(a_request->uri);
            a_request->uri = l_new_uri;
        }
    }

    return 0;
}

int ghttp_set_proxy(ghttp_request *a_request, char *a_uri)
{
    int l_rv = 0;

    if ((!a_request) || (!a_uri))
        return -1;
    /* set the uri */
    l_rv = http_uri_parse(a_uri, a_request->proxy);
    if (l_rv < 0)
        return -1;
    return 0;
}

int ghttp_set_type(ghttp_request *a_request, ghttp_type a_type)
{
    int l_return = 0;

    /* check to make sure that the args are ok */
    if (!a_request)
        return -1;
    /* switch on all of the supported types */
    switch (a_type)
    {
    case ghttp_type_get:
        a_request->req->type = http_req_type_get;
        break;
    case ghttp_type_options:
        a_request->req->type = http_req_type_options;
        break;
    case ghttp_type_head:
        a_request->req->type = http_req_type_head;
        break;
    case ghttp_type_post:
        a_request->req->type = http_req_type_post;
        break;
    case ghttp_type_put:
        a_request->req->type = http_req_type_put;
        break;
    case ghttp_type_delete:
        a_request->req->type = http_req_type_delete;
        break;
    case ghttp_type_trace:
        a_request->req->type = http_req_type_trace;
        break;
    case ghttp_type_connect:
        a_request->req->type = http_req_type_connect;
        break;
    case ghttp_type_propfind:
        a_request->req->type = http_req_type_propfind;
        break;
    case ghttp_type_proppatch:
        a_request->req->type = http_req_type_proppatch;
        break;
    case ghttp_type_mkcol:
        a_request->req->type = http_req_type_mkcol;
        break;
    case ghttp_type_copy:
        a_request->req->type = http_req_type_copy;
        break;
    case ghttp_type_move:
        a_request->req->type = http_req_type_move;
        break;
    case ghttp_type_lock:
        a_request->req->type = http_req_type_lock;
        break;
    case ghttp_type_unlock:
        a_request->req->type = http_req_type_unlock;
        break;
    default:
        l_return = -1;
        break;
    }
    return l_return;
}

int ghttp_set_body(ghttp_request *a_request, char *a_body, int a_len)
{
    /* check to make sure the request is there */
    if (!a_request)
        return -1;
    /* check to make sure the body is there */
    if ((a_len > 0) && (a_body == NULL))
        return -1;
    /* check to make sure that it makes sense */
    if ((a_request->req->type != http_req_type_post) &&
        (a_request->req->type != http_req_type_put) &&
        (a_request->req->type != http_req_type_proppatch) &&
        (a_request->req->type != http_req_type_propfind) &&
        (a_request->req->type != http_req_type_lock))
        return -1;
    /* set the variables */
    a_request->req->body     = a_body;
    a_request->req->body_len = a_len;
    return 0;
}

int ghttp_set_sync(ghttp_request *a_request, ghttp_sync_mode a_mode)
{
    if (!a_request)
        return -1;
    if (a_mode == ghttp_sync)
        a_request->conn->sync = HTTP_TRANS_SYNC;
    else if (a_mode == ghttp_async)
        a_request->conn->sync = HTTP_TRANS_ASYNC;
    else
        return -1;
    return 0;
}

int ghttp_prepare(ghttp_request *a_request)
{
    /* only allow http requests if no proxy has been set */
    if (!a_request->proxy->host && a_request->uri->proto &&
        strcmp(a_request->uri->proto, "http"))
        return 1;
    /* check to see if we have to set up the
       host information */
    if ((a_request->conn->host == NULL) ||
        (a_request->conn->host != a_request->uri->host) ||
        (a_request->conn->port != a_request->uri->port) ||
        (a_request->conn->proxy_host != a_request->proxy->host) ||
        (a_request->conn->proxy_port != a_request->proxy->port))
    {
        /* reset everything. */
        a_request->conn->host       = a_request->uri->host;
        a_request->req->host        = a_request->uri->host;
        a_request->req->full_uri    = a_request->uri->full;
        a_request->conn->port       = a_request->uri->port;
        a_request->conn->proxy_host = a_request->proxy->host;
        a_request->conn->proxy_port = a_request->proxy->port;
        a_request->conn->hostinfo   = NULL;
        /* close the socket if it looks open */
        if (a_request->conn->sock >= 0)
        {
            close(a_request->conn->sock);
            a_request->conn->sock = -1;
            a_request->connected  = 0;
        }
    }
    /* check to see if we need to change the resource. */
    if ((a_request->req->resource == NULL) ||
        (a_request->req->resource != a_request->uri->resource))
    {
        a_request->req->resource = a_request->uri->resource;
        a_request->req->host     = a_request->uri->host;
    }
    /* set the authorization header */
    if ((a_request->authtoken != NULL) && (strlen(a_request->authtoken) > 0))
    {
        http_hdr_set_value(a_request->req->headers, http_hdr_Authorization,
                           a_request->authtoken);
    }
    else
    {
        http_hdr_set_value(a_request->req->headers, http_hdr_WWW_Authenticate,
                           NULL);
    }
    /* set the proxy authorization header */
    if ((a_request->proxy_authtoken != NULL) &&
        (strlen(a_request->proxy_authtoken) > 0))
    {
        http_hdr_set_value(a_request->req->headers,
                           http_hdr_Proxy_Authorization,
                           a_request->proxy_authtoken);
    }
    http_req_prepare(a_request->req);
    return 0;
}

ghttp_status ghttp_process(ghttp_request *a_request)
{
    int l_rv = 0;

    if (a_request->proc == ghttp_proc_none)
        a_request->proc = ghttp_proc_request;
    if (a_request->proc == ghttp_proc_request)
    {
        if (a_request->connected == 0)
        {
            if (http_trans_connect(a_request->conn) < 0)
            {
                if (a_request->conn->error_type == http_trans_err_type_errno)
                    a_request->errstr = strerror(a_request->conn->error);
                else if (a_request->conn->error_type ==
                         http_trans_err_type_host)
                    a_request->errstr = http_trans_get_host_error(h_errno);
                return ghttp_error;
            }
            a_request->connected = 1;
        }
        l_rv = http_req_send(a_request->req, a_request->conn);
        if (l_rv == HTTP_TRANS_ERR)
            return ghttp_error;
        if (l_rv == HTTP_TRANS_NOT_DONE)
            return ghttp_not_done;
        if (l_rv == HTTP_TRANS_DONE)
        {
            a_request->proc = ghttp_proc_response_hdrs;
            if (a_request->conn->sync == HTTP_TRANS_ASYNC)
                return ghttp_not_done;
        }
    }
    if (a_request->proc == ghttp_proc_response_hdrs)
    {
        l_rv = http_resp_read_headers(a_request->resp, a_request->conn);
        if (l_rv == HTTP_TRANS_ERR)
            return ghttp_error;
        if (l_rv == HTTP_TRANS_NOT_DONE)
            return ghttp_not_done;
        if (l_rv == HTTP_TRANS_DONE)
        {
            a_request->proc = ghttp_proc_response;
            if (a_request->conn->sync == HTTP_TRANS_ASYNC)
                return ghttp_not_done;
        }
    }
    if (a_request->proc == ghttp_proc_response)
    {
        l_rv = http_resp_read_body(a_request->resp, a_request->req,
                                   a_request->conn);
        if (l_rv == HTTP_TRANS_ERR)
        {
            /* make sure that the connected flag is fixed and stuff */
            if (a_request->conn->sock == -1)
                a_request->connected = 0;
            return ghttp_error;
        }
        if (l_rv == HTTP_TRANS_NOT_DONE)
            return ghttp_not_done;
        if (l_rv == HTTP_TRANS_DONE)
        {
            /* make sure that the connected flag is fixed and stuff */
            if (a_request->conn->sock == -1)
                a_request->connected = 0;
            a_request->proc = ghttp_proc_none;
            return ghttp_done;
        }
    }
    return ghttp_error;
}

ghttp_current_status ghttp_get_status(ghttp_request *a_request)
{
    ghttp_current_status l_return;

    l_return.proc = a_request->proc;
    if (a_request->proc == ghttp_proc_request)
    {
        l_return.bytes_read  = a_request->conn->io_buf_io_done;
        l_return.bytes_total = a_request->conn->io_buf_alloc;
    }
    else if (a_request->proc == ghttp_proc_response_hdrs)
    {
        l_return.bytes_read  = 0;
        l_return.bytes_total = 0;
    }
    else if (a_request->proc == ghttp_proc_response)
    {
        if (a_request->resp->content_length > 0)
        {
            l_return.bytes_read = a_request->resp->body_len +
                                  a_request->conn->io_buf_alloc +
                                  a_request->resp->flushed_length;
            l_return.bytes_total = a_request->resp->content_length;
        }
        else
        {
            l_return.bytes_read = a_request->resp->body_len +
                                  a_request->conn->io_buf_alloc +
                                  a_request->resp->flushed_length;
            l_return.bytes_total = -1;
        }
    }
    else
    {
        l_return.bytes_read  = 0;
        l_return.bytes_total = 0;
    }
    return l_return;
}

void ghttp_flush_response_buffer(ghttp_request *a_request)
{
    http_resp_flush(a_request->resp, a_request->conn);
}

int ghttp_close(ghttp_request *a_request)
{
    if (!a_request)
        return -1;
    if (a_request->conn->sock >= 0)
    {
        close(a_request->conn->sock);
        a_request->conn->sock = -1;
    }
    a_request->connected = 0;
    return 0;
}

void ghttp_clean(ghttp_request *a_request)
{
    http_resp_destroy(a_request->resp);
    a_request->resp = http_resp_new();
    http_req_destroy(a_request->req);
    a_request->req = http_req_new();
    http_trans_buf_reset(a_request->conn);
    a_request->proc = ghttp_proc_none;
    return;
}

void ghttp_set_chunksize(ghttp_request *a_request, int a_size)
{
    if (a_request && (a_size > 0))
        a_request->conn->io_buf_chunksize = a_size;
}

void ghttp_set_header(ghttp_request *a_request, const char *a_hdr,
                      const char *a_val)
{
    http_hdr_set_value(a_request->req->headers, a_hdr, a_val);
}

const char *ghttp_get_header(ghttp_request *a_request, const char *a_hdr)
{
    return http_hdr_get_value(a_request->resp->headers, a_hdr);
}

int ghttp_get_header_names(ghttp_request *a_request, char ***a_hdrs,
                           int *a_num_hdrs)
{
    return http_hdr_get_headers(a_request->resp->headers, a_hdrs, a_num_hdrs);
}

const char *ghttp_get_error(ghttp_request *a_request)
{
    if (a_request->errstr == NULL)
        return "Unknown Error.";
    return a_request->errstr;
}

time_t ghttp_parse_date(char *a_date)
{
    if (!a_date)
        return 0;
    return (http_date_to_time(a_date));
}

int ghttp_status_code(ghttp_request *a_request)
{
    if (!a_request)
        return 0;
    return (a_request->resp->status_code);
}

const char *ghttp_reason_phrase(ghttp_request *a_request)
{
    if (!a_request)
        return 0;
    return (a_request->resp->reason_phrase);
}

int ghttp_get_socket(ghttp_request *a_request)
{
    if (!a_request)
        return -1;
    return (a_request->conn->sock);
}

char *ghttp_get_body(ghttp_request *a_request)
{
    if (!a_request)
        return NULL;
    if (a_request->proc == ghttp_proc_none)
        return (a_request->resp->body);
    if (a_request->proc == ghttp_proc_response)
    {
        if (a_request->resp->content_length > 0)
        {
            if (a_request->resp->body_len)
                return a_request->resp->body;
            else
                return a_request->conn->io_buf;
        }
        else
        {
            return a_request->resp->body;
        }
    }
    return NULL;
}

int ghttp_get_body_len(ghttp_request *a_request)
{
    if (!a_request)
        return 0;
    if (a_request->proc == ghttp_proc_none)
        return (a_request->resp->body_len);
    if (a_request->proc == ghttp_proc_response)
    {
        if (a_request->resp->content_length > 0)
        {
            if (a_request->resp->body_len)
                return a_request->resp->body_len;
            else
                return a_request->conn->io_buf_alloc;
        }
        else
        {
            return a_request->resp->body_len;
        }
    }
    return 0;
}

int ghttp_set_authinfo(ghttp_request *a_request, const char *a_user,
                       const char *a_pass)
{
    char *l_authtoken  = NULL;
    char *l_final_auth = NULL;
    char *l_auth64     = NULL;

    /* check our args */
    if (!a_request)
        return -1;
    /* if we have a NULL or zero length string in the username
       or password field, blitz the authinfo */
    if ((!a_user) || (strlen(a_user) < 1) || (!a_pass) || (strlen(a_pass) < 1))
    {
        if (a_request->username)
        {
            free(a_request->username);
            a_request->username = NULL;
        }
        if (a_request->password)
        {
            free(a_request->password);
            a_request->password = NULL;
        }
        if (a_request->authtoken)
        {
            free(a_request->authtoken);
            a_request->authtoken = NULL;
        }
        return 0;
    }
    /* encode the string using base64.  Usernames and passwords
       for basic authentication are encoded like this:
       username:password
       That's it.  Easy, huh?
    */
    /* enough for the trailing \0 and the : */
    l_authtoken = malloc(strlen(a_user) + strlen(a_pass) + 2);
    memset(l_authtoken, 0, (strlen(a_user) + strlen(a_pass) + 2));
    sprintf(l_authtoken, "%s:%s", a_user, a_pass);
    l_auth64 = http_base64_encode(l_authtoken);
    if (!l_auth64)
    {
        free(l_authtoken);
        return -1;
    }
    /* build the final header */
    l_final_auth = malloc(strlen(l_auth64) + strlen(basic_header) + 1);
    memset(l_final_auth, 0, (strlen(l_auth64) + strlen(basic_header) + 1));
    strcat(l_final_auth, basic_header);
    strcat(l_final_auth, l_auth64);
    free(l_auth64);
    free(l_authtoken);
    /* copy the strings into the request */

    if (a_request->username)
        free(a_request->username);
    if (a_request->password)
        free(a_request->password);
    if (a_request->authtoken)
        free(a_request->authtoken);
    a_request->username  = strdup(a_user);
    a_request->password  = strdup(a_pass);
    a_request->authtoken = l_final_auth;

    return 0;
}

int ghttp_set_proxy_authinfo(ghttp_request *a_request, const char *a_user,
                             const char *a_pass)
{
    char *l_authtoken  = NULL;
    char *l_final_auth = NULL;
    char *l_auth64     = NULL;

    /* check our args */
    if (!a_request)
        return -1;
    /* if we have a NULL or zero length string in the username
       or password field, blitz the authinfo */
    if ((!a_user) || (strlen(a_user) < 1) || (!a_pass) || (strlen(a_pass) < 1))
    {
        if (a_request->proxy_username)
        {
            free(a_request->proxy_username);
            a_request->proxy_username = NULL;
        }
        if (a_request->proxy_password)
        {
            free(a_request->proxy_password);
            a_request->proxy_password = NULL;
        }
        if (a_request->proxy_authtoken)
        {
            free(a_request->proxy_authtoken);
            a_request->proxy_authtoken = NULL;
        }
        return 0;
    }
    /* encode the string using base64.  Usernames and passwords
       for basic authentication are encoded like this:
       username:password
       That's it.  Easy, huh?
    */
    /* enough for the trailing \0 and the : */
    l_authtoken = malloc(strlen(a_user) + strlen(a_pass) + 2);
    memset(l_authtoken, 0, (strlen(a_user) + strlen(a_pass) + 2));
    sprintf(l_authtoken, "%s:%s", a_user, a_pass);
    l_auth64 = http_base64_encode(l_authtoken);
    if (!l_auth64)
    {
        free(l_authtoken);
        return -1;
    }
    /* build the final header */
    l_final_auth = malloc(strlen(l_auth64) + strlen(basic_header) + 1);
    memset(l_final_auth, 0, (strlen(l_auth64) + strlen(basic_header) + 1));
    strcat(l_final_auth, basic_header);
    strcat(l_final_auth, l_auth64);
    free(l_auth64);
    free(l_authtoken);
    /* copy the strings into the request */
    if (a_request->proxy_username)
        free(a_request->proxy_username);
    if (a_request->proxy_password)
        free(a_request->proxy_password);
    if (a_request->proxy_authtoken)
        free(a_request->proxy_authtoken);
    a_request->proxy_username  = strdup(a_user);
    a_request->proxy_password  = strdup(a_pass);
    a_request->proxy_authtoken = l_final_auth;

    return 0;
}
